package cn.ztydata.teachingsystem.heaven.service;

import cn.ztydata.teachingsystem.common.Page;
import cn.ztydata.teachingsystem.exception.ServiceException;
import cn.ztydata.teachingsystem.heaven.common.WebContext;
import cn.ztydata.teachingsystem.heaven.cons.ExerciseStatus;
import cn.ztydata.teachingsystem.heaven.cons.ExerciseType;
import cn.ztydata.teachingsystem.heaven.cons.FileNameCode;
import cn.ztydata.teachingsystem.heaven.cons.Roles;
import cn.ztydata.teachingsystem.heaven.dao.*;
import cn.ztydata.teachingsystem.heaven.dao.redis.*;
import cn.ztydata.teachingsystem.heaven.entity.Department;
import cn.ztydata.teachingsystem.heaven.entity.KnowledgePoint;
import cn.ztydata.teachingsystem.heaven.entity.Resource;
import cn.ztydata.teachingsystem.heaven.entity.Specialty;
import cn.ztydata.teachingsystem.heaven.entity.exercise.*;
import cn.ztydata.teachingsystem.heaven.entity.test.StudentAnswer;
import cn.ztydata.teachingsystem.heaven.entity.test.TestExercise;
import cn.ztydata.teachingsystem.heaven.util.FileConverter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringEscapeUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.text.DecimalFormat;
import java.util.*;


/**
 * 习题库服务类
 * Created by lyj on 2015/2/4.
 */
@Service
public class ExerciseService extends BaseService {
    @Autowired
    private KpService kpService;
    @Autowired
    private ExerciseDao exerciseDao;
    @Autowired
    private ExerciseResDao exerciseResDao;
    @Autowired
    private ExerciseKpDao exerciseKpDao;
    @Autowired
    private SpecialtyDao specialtyDao;
    @Autowired
    private DepartmentDao departmentDao;
    @Autowired
    private ExerciseAnalysisDao exerciseAnalysisDao;
    @Autowired
    private ExerciseTipDao exerciseTipDao;
    @Autowired
    private ExerciseBlankDao exerciseBlankDao;
    @Autowired
    private ExerciseOptionDao exerciseOptionDao;
    @Autowired
    private RoleDao roleDao;
    @Autowired
    private ExerciseRelationDao exerciseRelationDao;
    @Autowired
    private BlankSolutionDao blankSolutionDao;
    @Autowired
    private ResourceDao resourceDao;
    @Autowired
    private WebContext webContext;
    @Autowired
    private ExerciseCountDao exerciseCountDao;
    @Autowired
    private FileConverter fileConverter;
    @Autowired
    private KpExerciseDao kpExerciseDao;
    @Autowired
    private StudentAnswerRedisDao studentAnswerRedisDao;

    @Autowired
    private TestService testService;
    @Autowired
    private StudentAnswerService studentAnswerService;

    @Autowired
    private SpecialtyService specialtyService;

    /**
     * 统计不同类型习题的总数
     *
     * @author chenxin
     * @since 2015/2/4
     */
    @Cacheable(value = "teachingSystem:cache:count", key = "'exercise'")
    public Map<String, Object> getStatistics() {
        //获取统计结果
        List<Map<String, Object>> statisticsResult = exerciseDao.findTotalByType();
        //转换统计结果格式，便于重组
        Map<Integer, Long> typeTotal = Maps.newHashMap();

        log.debug("循环统计结果");
        for (Map<String, Object> item : statisticsResult) {
            typeTotal.put((int) item.get("type"), (long) item.get("total"));
        }

        Map<String, Object> result = Maps.newHashMap();
        //定义习题总数量
        long total = 0;
        //获取各种习题类型总数
        List<Map<String, Object>> info = Lists.newArrayList();

        log.debug("循环各种题型");
        for (int i = ExerciseType.SINGLE_CHOICE; i <= ExerciseType.READING_COMPREHENSION_MULTI; i++) {
            Map<String, Object> infoItem = Maps.newHashMap();

            infoItem.put("type", i);
            Long count = typeTotal.get(i);
            count = count == null ? 0 : count;
            infoItem.put("count", count);

            info.add(infoItem);

            total += count;
        }

        result.put("total", total);
        result.put("info", info);

        log.debug("return 习题统计总数");
        return result;
    }


    /**
     * 审核/删除习题
     *
     * @param mapAudit
     * @author lyj
     * @since 2015-2-4
     */
    @CacheEvict(value = "teachingSystem:cache:count", key = "'exercise'")
    public void auditExercise(Map<String, Object> mapAudit) {
        //获取当前登录员工ID为审核人ID
        mapAudit.put("auditorId", webContext.getUserId());

        //如果是操作我的题库里的数据不用进行权限验证
        if ((int)mapAudit.get("tag") == 2) {
            log.debug("如果是操作我的题库里的数据不用进行权限验证: mapAudit.get(\"tag\") == 2");

            //如过不是执行的删除操作
            if ((int)mapAudit.get("status") != 3) {
                throw new ServiceException("操作失败！");
            }

            //判断是否被引用
            if (exerciseDao.checkExercise((int) mapAudit.get("id")) > 0) {

                //判断作废是否成功
                if (exerciseDao.auditExercise(mapAudit) != 1) {
                    log.warn("作废习题失败：exerciseId>>>{},auditorId>>>{}", mapAudit.get("id"), mapAudit.get("authorId"));
                    throw new ServiceException("习题现被作业或测试使用,且作废失败！");
                }

                throw new ServiceException("习题现被作业或测试使用,已将该习题设置为无效数据");
            } else {
                //判断删除是否成功
                if (exerciseDao.deleteByExerciseId((int) mapAudit.get("id")) != 1) {
                    log.debug("删除未成功：exerciseDao.auditExercise(mapAudit) != 1");
                    log.warn("删除习题失败：exerciseId>>>{},auditorId>>>{}", mapAudit.get("id"), mapAudit.get("authorId"));
                    throw new ServiceException();
                }
            }


        } else {
            log.debug("如果不是操作我的题库里的数据进行权限验证");

            //判断当前登录员工角色（只有“专业负责人”、“超级管理员”可以审核）
            if (!(webContext.getRole() == Roles.SUPER_ADMIN || webContext.getRole() == Roles.SPEC_LEADER)) {
                log.debug("不是“专业负责人”、“超级管理员”：!(webContext.getRole() == Roles.SUPER_ADMIN || webContext.getRole() == Roles.SPEC_LEADER)");
                log.warn("没有权限操作该方法：userId>>>{}", webContext.getUserId());
                throw new ServiceException();
            }

            //判断数据是否是该专业负责人负责的数据
            if (!judgeRole(Long.parseLong(mapAudit.get("majorId").toString()))) {
                log.debug("不负责该专业的数据：!judgeRole(Long.parseLong(mapAudit.get(\"majorId\").toString()))");
                log.warn("操作的习题专业不属于登录用户管理的专业：majorId->{}", mapAudit.get("majorId"));
                throw new ServiceException();
            }

            if ((int)mapAudit.get("status") == 3){
                log.debug("如果状态等于3：mapAudit.get(\"status\") == 3");
                if(exerciseDao.deleteByExerciseId((int)mapAudit.get("id")) != 1){
                    log.debug("删除习题失败:exerciseDao.deleteByExerciseId((long)mapAudit.get(\"id\")) != 1");
                    log.warn("根据id删除习题失败：exerciseId->{}", mapAudit.get("id"));
                    throw new ServiceException("删除习题失败！");
                }
            } else {
                //判断审核是否成功
                if (exerciseDao.auditExercise(mapAudit) != 1) {
                    log.debug("审核、删除未成功：exerciseDao.auditExercise(mapAudit) != 1");
                    log.warn("审核/删除习题失败：exerciseId>>>{},auditorId>>>{}", mapAudit.get("id"), mapAudit.get("authorId"));
                    throw new ServiceException();
                }
            }
        }
    }


    /**
     * 当前登录用户是否有权限审核该习题
     *
     * @param specId 专业ID
     * @return boolean
     * @author lyj
     * @since 2015-2-9
     */
    public boolean judgeRole(Long specId) {

        //如果是超级管理员直接返回true，如果不是比对专业ID
        if (webContext.getRole() == Roles.SUPER_ADMIN) {
            log.debug("如果是超级管理员直接返回true：webContext.getRole() == Roles.SUPER_ADMIN");
            return true;
        }

        //根据员工ID和角色获取专业ID集合（一个专业负责人可以负责多个专业）
        List<Long> userSpecIds = roleDao.getSpecIdByRoleId(webContext.getUserId(), webContext.getRole());

        if (userSpecIds.size() == 0) {
            log.debug("获取专业负责人负责专业ID集合大小为0：userSpecIds.size() == 0");
            log.warn("判断当前用户是否有权审核该习题失败 specId:->{},userId:->{},roleId:->{}", specId, webContext.getUserId(), webContext.getRole());
            throw new ServiceException();
        }

        log.debug("判断当前登录用户是否有权限审核该习题");
        return userSpecIds.contains(specId);

    }

    /**
     * 专业习题统计
     *
     * @param deptCode 系部编码
     * @param tag      标记（0：系统题库 1：待审核题库  2：我的题库）
     * @return
     * @author lyj
     * @since 2015-2-5
     */

    public List<Map<String, Object>> getSpecExercises(String deptCode, int tag, String search) {
        List<Long> specIds = new ArrayList<>();
        int isOpen = 1; //公开的习题
        int status = ExerciseStatus.EXERCISE_APPROVED; //审核通过的
        long creatorId = 0; //习题创建者ID

        //如果tag=1(待审核题库)根据员工角色获取其所能管理的专业ID
        if (tag == 1) {
            log.debug("如果tag=1(待审核题库)根据员工角色获取其所能管理的专业ID:tag == 1");
            //根据用户ID，用户角色获取用户所管理的专业ID集合。
            if (webContext.getRole() != Roles.SUPER_ADMIN) {

                log.debug("根据用户ID，用户角色获取用户所管理的专业ID集合:webContext.getRole() != Roles.SUPER_ADMIN");
                specIds = roleDao.getSpecIdByRoleId(webContext.getUserId(), webContext.getRole());
            }

            status = ExerciseStatus.NOT_AUDIT; //状态是未审核

            return bindSepc(deptCode, specIds, isOpen, status, creatorId, tag, search);

        } else if (tag == 2) { //我的习题

            log.debug("如果tag=2(我的题库)：tag == 2");
            //获取用户创建的习题所属的专业ID
            specIds = exerciseDao.getSpecIds(webContext.getUserId());

            isOpen = 9; //等于9不用判断该条件
            status = 9; //等于9不用判断该条件
            creatorId = webContext.getUserId();
        } else if (tag == 3){ //已驳回习题

            //根据用户ID，用户角色获取用户所管理的专业ID集合。
            if  (webContext.getRole() != Roles.SUPER_ADMIN){

                log.debug("根据用户ID，用户角色获取用户所管理的专业ID集合:webContext.getRole() != Roles.SUPER_ADMIN");
                specIds = roleDao.getSpecIdByRoleId(webContext.getUserId(), webContext.getRole());
            }

            status = ExerciseStatus.EXERCISE_REJECTED; //状态是未审核
            creatorId = webContext.getUserId();

            return bindSepc(deptCode, specIds, isOpen, status, creatorId, tag, search);
        }

        log.debug("根据专业ID获取专业统计列表（如果tag=0，获取所选系部下的所有专业）");
        //根据专业ID获取专业统计列表（如果tag=0（系统），获取所选系部下的所有专业）
        return specialtyDao.getSpecExercises(deptCode, specIds, isOpen, status, creatorId, tag, search);
    }

    /**
     * 统计专业习题列表（待审核习题列表，已驳回习题列表）
     * @param deptCode
     * @param specIds
     * @param isOpen
     * @param status
     * @param creatorId
     * @param tag
     * @param search
     * @return
     * @author lyj
     * @since 2015-3-10
     */
    public List<Map<String, Object>> bindSepc(String deptCode, List<Long> specIds, int isOpen, int status, long creatorId, int tag, String search) {
        //获取有数据的专业集合in
        List<Map<String, Object>> mapCount = specialtyDao.getSpecExercises(deptCode, specIds, isOpen, status, creatorId, tag, search);

        //去除已查询到习题数量的专业ID
        log.debug("去除已查询到习题数量的专业ID:Map<String, Object> map : mapCount");
        for (Map<String, Object> map : mapCount) {
            //去除指定的专业ID
            if (specIds.contains(map.get("id"))) {
                log.debug("去除指定的专业ID:specIds.contains(map.get(\"id\"))");
                specIds.remove(map.get("id"));
            }
        }

        //获取无习题的专业集合
        List<Map<String, Object>> mapList = specialtyDao.getSpecListById(specIds);

        //填充0
        for (Map<String, Object> map : mapList) {
            map.put("count", 0);
        }
        //合并两个集合
        mapCount.addAll(mapList);


        log.debug("绑定专业习题数量");
        return mapCount;
    }


    /**
     * 获取系部列表
     *
     * @param tag 标记（0：系统题库 1：待审核题库  2：我的题库）
     * @param
     * @return List
     * @author lyj
     * @since 2015-2-5
     */
    public List<Map<String, Object>> getDepartExercises(int tag, String search) {
        List<Long> deptIds = new ArrayList<>();
        List<Long> specIds = new ArrayList<>();
        int isOpen = 1; //公开的习题
        int status = ExerciseStatus.EXERCISE_APPROVED; //审核通过的
        long creatorId = 0; //习题创建者ID

        if (tag == 1) { //如果tag=1(待审核题库)根据员工角色获取其所能管理的专业，然后根据专业获取对应的系部
            log.debug("获取待审核题库：tag == 1");

            //根据用户ID，用户角色获取用户所管理的专业ID集合。
            if  (webContext.getRole() != Roles.SUPER_ADMIN) {

                log.debug("根据用户ID，用户角色获取用户所管理的专业ID集合:webContext.getRole() != Roles.SUPER_ADMIN");
                //根据用户ID，用户角色获取用户所管理的专业ID集合。
                specIds = roleDao.getSpecIdByRoleId(webContext.getUserId(), webContext.getRole());
                //根据专业ID获取系部ID集合。
                deptIds = departmentDao.getDepartIds(specIds);
            }

            status = ExerciseStatus.NOT_AUDIT; //状态是未审核

        } else if (tag == 2) { //如果tag=2（我的题库）跟根据员工所创建的习题绑定的系部ID，获取系部列表
            log.debug("获取我的题库：tag == 2");

            //获取用户创建的习题所属的系部ID
            deptIds = exerciseDao.getDeptIds(webContext.getUserId());

            isOpen = 9; //等于9不用判断该条件
            status = 9; //等于9不用判断该条件
            creatorId = webContext.getUserId();

        } else if (tag == 3){ //如果tag=3(已驳回列表)获取登录员工审核驳回的习题列表

            //根据用户ID，用户角色获取用户所管理的专业ID集合。
            if  (webContext.getRole() != Roles.SUPER_ADMIN) {

                log.debug("根据用户ID，用户角色获取用户所管理的专业ID集合:webContext.getRole() != Roles.SUPER_ADMIN");
                //根据用户ID，用户角色获取用户所管理的专业ID集合。
                specIds = roleDao.getSpecIdByRoleId(webContext.getUserId(), webContext.getRole());
                //根据专业ID获取系部ID集合。
                deptIds = departmentDao.getDepartIds(specIds);
            }

            status = ExerciseStatus.EXERCISE_REJECTED;
            creatorId = webContext.getUserId();
        }

        //根据系部ID获取系部统计列表（如果tag=0，获取所有的系部）
        log.debug("获取系部习题数");
        return departmentDao.getDepartExercises(deptIds, specIds, isOpen, status, creatorId, tag, search);

    }


    /**
     * 统计习题类型习题数
     *
     * @param deptId 系部ID
     * @param specId 专业ID
     * @param tag    标记（0：系统题库 1：待审核题库  2：我的题库）
     * @param status 习题状态 （0：待审核 1：审核通过 2：驳回 3：删除 4：全部）
     * @param isOpen 是否公开 （0：不公开 1:公开）
     * @return
     * @author lyj
     * @since 2015-2-28
     */
    public List<Map<String, Object>> getTypeExercises(long deptId, long specId, int tag, int status, int isOpen) {
        long creatorId = 0;//创建人ID

        //如果是我的题库，创建人是过滤条件
        if (tag == 2) {
            log.debug("如果是我的题库，创建人是过滤条件:tag == 2");
            creatorId = webContext.getUserId();
        }

        log.debug("统计习题类型习题数");
        return exerciseDao.getTypeExercises(deptId, specId, status, isOpen, creatorId);
    }


    /**
     * 习题常规解析
     *
     * @param
     * @return List
     * @author lyj
     * @since 2015-2-5
     */
    public List<ExerciseAnalysis> getAnalysis(long exerciseId) {
        log.debug("获取习题常规解析");

        return exerciseAnalysisDao.getAnalysis(exerciseId);
    }

    /**
     * 习题引用资源解析
     *
     * @param
     * @return List
     * @author lyj
     * @since 2015-2-5
     */
    public List<Resource> getResAnalysis(long exerciseId) {
        //根据习题ID获取所有的资源ID
        List<Long> rscIds = exerciseResDao.getResIds(exerciseId);

        log.debug("习题引用资源解析");
        return resourceDao.getList(rscIds);
    }


    /**
     * 根据习题ID获取知识点
     *
     * @param
     * @return List
     * @author lyj
     * @since 2015-2-5
     */
    public List<KnowledgePoint> getKnowledge(Long exerciseId) {
        //根据习题ID获取所有的知识点ID
        List<Long> kpIds = exerciseKpDao.getKpIds(exerciseId);

        log.debug("根据习题ID获取知识点");
        //根据id集合获取知识点信息
        return kpService.getAll(kpIds);
    }


    /**
     * 新增习题
     *
     * @param exercise 习题
     * @author wyh
     * @since 2015-2-5
     */
    @CacheEvict(value = "teachingSystem:cache:count", key = "'exercise'")
    @Transactional
    public List<Long> saveExercise(Exercise exercise) {
        int type = exercise.getType();//题型

        exercise.setCreatorId(webContext.getUserId());//设置创建人ID

        //根据专业ID获取系部ID
        long deptId = departmentDao.getIdBySpecId(exercise.getSpecId());
        if (deptId == 0) {
            log.debug("根据专业ID获取系部ID失败：deptId == 0");
            log.warn("获取系部ID错误: specId()->{}", exercise.getSpecId());
            throw new ServiceException("获取系部ID错误");
        }
        exercise.setDeptId(deptId);//设置系部ID

        //判断如果是超管直接通过否则为未审批
        exercise.setStatus(webContext.getRole() == Roles.SUPER_ADMIN ? ExerciseStatus.EXERCISE_APPROVED : ExerciseStatus.NOT_AUDIT);
        //判断如果是专业负责人，判断此题是否拥有其负责
        if (roleDao.checkRole(webContext.getUserId(), Roles.SPEC_LEADER) && specialtyService.checkUserIsLeader(webContext.getUserId(), exercise.getSpecId())) {
            exercise.setStatus(ExerciseStatus.EXERCISE_APPROVED);
        }

        //去除题目中的html标签
        exercise.setShowTopic(htmlToText(exercise.getTopic()));

        //插入题目信息
        if (exerciseDao.saveExercise(exercise) < 1) {
            log.debug("插入题目信息失败：exerciseDao.saveExercise(exercise) < 1");
            log.warn("新增习题失败: exerciseId->{}", exercise.getId());
            throw new ServiceException("新增习题失败");
        }

        //保存习题与知识点关系
        saveExerciseKps(exercise.getId(), exercise.getKps());

        //保存习题与资源关系
        saveExerciseResIds(exercise.getId(), exercise.getResourceIds());

        //批量习题与保存习题提示
        saveExerciseTips(exercise.getId(), exercise.getTipList());

        //批量习题与保存习题解析
        saveExerciseAnalysises(exercise.getId(), exercise.getAnalysisList());


        //如果是单选、多选、判断，需要插入选项信息
        if (type == ExerciseType.SINGLE_CHOICE || type == ExerciseType.MULTIPLE_CHOICE || type == ExerciseType.TRUE_OR_FALSE) {
            log.debug("习题类型是单选、多选、判断：type == ExerciseType.SINGLE_CHOICE || type == ExerciseType.MULTIPLE_CHOICE || type == ExerciseType.TRUE_OR_FALSE");
            saveOptions(exercise.getId(), exercise.getOptions());
        }

        //如果是填空，需要插入填空信息以及填空答案
        if (type == ExerciseType.COMPLETION) {
            log.debug("习题类型是填空：type == ExerciseType.COMPLETION");
            saveBlanks(exercise.getId(), exercise.getScore(), exercise.getBlanks());
        }

        //如果是多题阅读理解题，分别保存每个小题
        if (type == ExerciseType.READING_COMPREHENSION_MULTI) {
            log.debug("习题类型是多题阅读理解：type == ExerciseType.READING_COMPREHENSION_MULTI");
            saveSubexercises(exercise.getId(), exercise.getScore(), exercise.getSubExercises());
        }

        List<Long> ids = new ArrayList<>();

        //返回主习题ID以及子习题ID的List
        ids.add(exercise.getId());
        if(exercise.getSubExercises() != null && !exercise.getSubExercises().isEmpty()){
            for(Exercise subExercise : exercise.getSubExercises()){
                ids.add(subExercise.getId());
            }
        }
        return ids;
    }

    /**
     * 保存习题相关的知识点
     *
     * @param exerciseId 习题ID
     * @param kps        知识点
     * @author wyh
     * @since 2015-2-10
     */
    public void saveExerciseKps(long exerciseId, List<KnowledgePoint> kps) {
        //判断知识点为空，是空抛异常
        if (kps == null) {
            log.debug("知识点List为null：kps == null");
            throw new ServiceException("绑定知识点不能为空");
        }

        //保存知识点关系
        log.debug("保存习题与知识点的关系：for (KnowledgePoint kp : kps)");

        for (KnowledgePoint kp : kps) {
            exerciseKpDao.saveKp(exerciseId, kp.getId());
        }
    }

    /**
     * 保存习题相关的资源
     *
     * @param exerciseId 习题ID
     * @param resIds     资源ID集合
     * @author wyh
     * @since 2015-2-10
     */
    public void saveExerciseResIds(long exerciseId, List<Long> resIds) {
        //保存资源关系
        if (resIds != null && !resIds.isEmpty()) {
            log.debug("资源ID不为空：resIds != null && !resIds.isEmpty()");

            log.debug("保存习题与资源的关系：for (long resId : resIds)");
            for (long resId : resIds) {
                exerciseResDao.saveRes(exerciseId, resId);
            }
        }
    }

    /**
     * 保存习题相关的提示
     *
     * @param exerciseId 习题ID
     * @param tips       习题提示集合
     * @author wyh
     * @since 2015-2-10
     */
    public void saveExerciseTips(long exerciseId, List<ExerciseTip> tips) {
        //批量保存习题提示
        if (tips != null && !tips.isEmpty()) {
            log.debug("习题提示不为空：tips != null  && !tips.isEmpty()");
            exerciseTipDao.saveTips(exerciseId, tips);
        }
    }

    /**
     * 保存习题相关的解析
     *
     * @param exerciseId 习题ID
     * @param analysises 习题解析集合
     * @author wyh
     * @since 2015-2-10
     */
    public void saveExerciseAnalysises(long exerciseId, List<ExerciseAnalysis> analysises) {
        //批量保存习题解析
        if (analysises != null && !analysises.isEmpty()) {
            log.debug("习题解析不为空：analysises != null && !analysises.isEmpty()");
            exerciseAnalysisDao.saveAnalysises(exerciseId, analysises);
        }
    }

    /**
     * 附件转换服务
     *
     * @param exerciseId
     * @param downloadAddress
     * @author wyh
     * @since 2015-3-8
     */
    public void transformAttachment(long exerciseId, String downloadAddress){
        //判断是否地址为空，判断文件类型
        if (downloadAddress != null && !downloadAddress.isEmpty()
                && Arrays.asList(new String[]{".doc", ".docx", ".rtf", ".xls", ".xlsx", ".ppt", ".pptx"}).contains(downloadAddress.substring(downloadAddress.lastIndexOf(".")))) {
            log.debug("附件下载地址不存在或者转换格式不正确：downloadAddress != null && !downloadAddress.isEmpty() && " +
                    "Arrays.asList(new String[]{\".doc\", \".docx\", \".rtf\", \".xls\", \".xlsx\", \".ppt\", \".pptx\"}).contains(downloadAddress.substring(downloadAddress.lastIndexOf(\".\")))");

            try {
                fileConverter.newTask("exercise:" + FileNameCode.exerciseMap.get(1) + ":" + exerciseId);
            } catch (IOException ex) {
                log.debug("习题附件转换时IO异常");
                log.warn("习题附件转换时IO异常: exerciseId->{}", exerciseId);
            }
        }
    }
    /**
     * 保存习题选项
     *
     * @param exerciseId 习题ID
     * @param options    选项集合
     * @author wyh
     * @since 2015-2-12
     */
    public void saveOptions(long exerciseId, List<ExerciseOption> options) {
        if (options == null || options.isEmpty()) {
            log.debug("选项为空：options == null || options.isEmpty()");
            throw new ServiceException("单选、多选、判断的选项不可为空");
        }
        exerciseOptionDao.saveExerciseOptions(exerciseId, options);
    }


    /**
     * 保存填空，批量保存填空答案
     *
     * @param exerciseId 习题ID
     * @param blanks     填空集合
     * @author wyh
     * @since 2015-2-6
     */
    public void saveBlanks(long exerciseId, long score, List<ExerciseBlank> blanks) {
        //判断分值是否为填空答案数的倍数
        if(score != 0){
            int count = 0; //填空答案总数
            for(ExerciseBlank blank : blanks){
                count += blank.getAnswerCount();
            }
            if( score % count != 0){
                throw new ServiceException("填空题分值必须为填空答案总数的整数倍");
            }
        }

        //判断填空是否为空
        if (blanks == null || blanks.isEmpty()) {
            log.debug("填空为空：blanks == null || blanks.isEmpty()");
            throw new ServiceException("填空题的填空不可为空");
        }

        //循环所有空
        log.debug("单个保存每个填空以及其答案：for (ExerciseBlank blank : blanks)");
        for (ExerciseBlank blank : blanks) {
            blank.setExerciseId(exerciseId);

            //判断答案数是否合法
            if (blank.getAnswerCount() > blank.getSolutions().size()) {
                log.debug("填空需要的答案数大于答案总数：blank.getAnswerCount() > blank.getSolutions().size()");

                log.warn("新增失败: blankExerciseId->{}", exerciseId);
                throw new ServiceException("填空答案数不合法");
            }

            //新增填空信息
            if (exerciseBlankDao.saveExerciseBlank(blank) < 1) {
                log.debug("新增填空信息：exerciseBlankDao.saveExerciseBlank(blank) < 1");
                log.warn("新增失败: blankExerciseId->{}", exerciseId);
                throw new ServiceException("新增习题时出错");
            }

            //循环setID并转换html
            for(BlankSolution solution : blank.getSolutions()){
                solution.setBlankId(blank.getId());
                solution.setTextSolution(htmlToText(solution.getHtmlSolution()).trim());
            }

            //批量新增填空答案
            blankSolutionDao.saveBlankSolutions(blank.getSolutions());
        }
    }

    /**
     * 保存子习题
     *
     * @param exerciseId   主习题ID
     * @param subExercises 子习题集合
     * @author wyh
     * @since 2015-2-6
     */
    public void saveSubexercises(long exerciseId, long score, List<Exercise> subExercises) {
        //判断主习题分数是否等于子分数总和
        if(score != 0){
            int total = 0; //子习题分数总和
            for(Exercise subExercise : subExercises){
                total += subExercise.getScore();
            }
            if(total != score){
                log.warn("主习题分数与子习题分数综合不一致:score->{},total->{}", score, total);
                throw new ServiceException("主习题分数与子习题分数综合不一致");
            }
        }

        //判断子习题为空
        if (subExercises == null || subExercises.isEmpty()) {
            log.debug("子习题为空：subExercises == null || subExercises.isEmpty()");
            throw new ServiceException("多题阅读理解题的子习题不可为空");
        }

        //循环子习题
        log.debug("保存每个子习题：for (Exercise subExercise : subExercises)");
        for (Exercise subExercise : subExercises) {
            subExercise.setParentId(exerciseId);//set主习题ID

            //去除题目中的html标签
            subExercise.setShowTopic(htmlToText(subExercise.getTopic()));

            //保存子习题
            if (exerciseDao.saveSubexercise(subExercise) < 1) {
                log.debug("新增子习题失败:exerciseDao.saveSubexercise(subExercise) < 1");
                log.warn("新增子习题失败: exerciseId->{}", exerciseId);
                throw new ServiceException("新增子习题时出错");
            }

            //生成附件显示地址
            transformAttachment(subExercise.getId(), subExercise.getDownloadAddress());

            int type = subExercise.getType();//题型
            //如果是单选、多选、判断，需要插入选项信息
            if (type == ExerciseType.SINGLE_CHOICE || type == ExerciseType.MULTIPLE_CHOICE || type == ExerciseType.TRUE_OR_FALSE) {
                log.debug("习题类型是单选、多选、判断：type == ExerciseType.SINGLE_CHOICE || type == ExerciseType.MULTIPLE_CHOICE || type == ExerciseType.TRUE_OR_FALSE");
                saveOptions(subExercise.getId(), subExercise.getOptions());
            }

            //如果是填空，需要插入填空信息以及填空答案
            if (type == ExerciseType.COMPLETION) {
                log.debug("习题类型是填空：type == ExerciseType.COMPLETION");
                saveBlanks(subExercise.getId(), subExercise.getScore(), subExercise.getBlanks());
            }

            //保存子习题与主习题关系
            exerciseRelationDao.saveSonExercise(exerciseId, subExercise.getId());
        }
    }

    /**
     * 习题列表
     *
     * @param pageNum  当前页码
     * @param pageSize 每页条数
     * @param deptId   系部ID
     * @param specId   专业ID
     * @param tag      标记(0:习题库 1:待审核题库 2:我的题库)
     * @param search   关键字
     * @param type     习题类型
     * @return list
     * @author lyj
     * @since 2015-2-7
     */

    public Page<Exercise> getExerciseList(int pageNum, int pageSize, long deptId, long specId, int tag, int status, int isOpen, String search, List<Long> type) {
        Page<Exercise> pages = null;

        //声明存储专业ID的容器
        List<Long> specIds = new ArrayList<>();
        //声明存储系部ID的容器
        List<Long> deptIds = new ArrayList<>();

        if (tag == 0) {

            log.debug("如果tag=0(系统习题)：tag == 0");
            checkParameter(specIds, deptIds, specId, deptId);
            //getExerciseList参数（当前页码、每页条数、系部ID、专业ID集合、习题创建者、习题状态、公开私有(0私有  1公开)、关键字、习题类型）
            pages = exerciseDao.getExerciseList(pageNum, pageSize, deptIds, specIds, 0, ExerciseStatus.EXERCISE_APPROVED, 1, search, type, tag);

        } else if (tag == 1 && webContext.getRole() == Roles.SUPER_ADMIN) { //超级管理员获取待审核习题库

            log.debug("如果是超级管理员获取待审核列表：tag == 1 && webContext.getRole() == Roles.SUPER_ADMIN");
            checkParameter(specIds, deptIds, specId, deptId);
            pages = exerciseDao.getExerciseList(pageNum, pageSize, deptIds, specIds, 0, ExerciseStatus.NOT_AUDIT, 1, search, type, tag);

        } else if (tag == 1 && webContext.getRole() == Roles.SPEC_LEADER) { //专业负责人获取待审核习题库

            log.debug("如果是专业负责人获取待审核列表：tag == 1 && webContext.getRole() == Roles.SPEC_LEADER");
            //验证是否为登录用户所管理的专业
            fillterParameter(deptIds, specIds, deptId, specId);
            pages = exerciseDao.getExerciseList(pageNum, pageSize, deptIds, specIds, 0, ExerciseStatus.NOT_AUDIT, 1, search, type, tag);

        } else if (tag == 2) { //获取我的习题库列表

            log.debug("获取我的习题库列表：tag == 2");
            checkParameter(specIds, deptIds, specId, deptId);
            pages = exerciseDao.getExerciseList(pageNum, pageSize, deptIds, specIds, webContext.getUserId(), status, isOpen, search, type, tag);

        } else if (tag == 3){ //获取已驳回习题列表

            log.debug("获取已驳回习题列表：tag == 3");
            if (webContext.getRole() == Roles.SUPER_ADMIN){ //如果是超级管理员获取列表
                log.debug("如果是超级管理员获取列表:webContext.getRole() == Roles.SUPER_ADMIN");
                checkParameter(specIds, deptIds, specId, deptId);
            }else {
                log.debug("如果是专业负责人获取待审核列表,验证是否为登录用户所管理的专业");
                //如果是专业负责人获取待审核列表,验证是否为登录用户所管理的专业
                fillterParameter(deptIds, specIds, deptId, specId);
            }
            pages = exerciseDao.getExerciseList(pageNum, pageSize, deptIds, specIds, webContext.getUserId(), status, isOpen, search, type, tag);

        } else {
            log.debug("参数错误");
            log.warn("参数错误：tag->{},role->{}", tag, webContext.getRole());
            throw new ServiceException("参数错误！");
        }

        //四舍五入df.format(0.356) = 36%
        DecimalFormat df = new DecimalFormat("##%");

        //遍历每道习题
        log.debug("遍历每道习题");
        for (Exercise exercise : pages.getItems()) {

            //根据习题ID获取前4个知识点ID
            List<Long> kpIds = exerciseKpDao.getKpIds(exercise.getId());

            //根据ID集合获取知识点集合
            exercise.setKps(kpService.getAll(kpIds));

            //获取习题使用次数、回答次数、回答正确次数（注意参数顺序就是获取的数据顺序）
            List<Long> longList = exerciseCountDao.getMulti(exercise.getId(), ExerciseCountDao.USE_TOTAL, ExerciseCountDao.ANSWER_TOTAL, ExerciseCountDao.RIGHT_TOTAL);
            //习题热度
            exercise.setUseTotal(longList.get(0));

            //习题正确率 = 回答正确数 除以 回答总数
            if (longList.get(1) != 0) {
                log.debug("习题正确率 = 回答正确数 除以 回答总数:longList.get(1) != 0");
                //回答正确数 除以 回答总数
                exercise.setAccuracy(df.format((double)longList.get(2) / longList.get(1)));
            } else {
                log.debug("习题正确率为0%");
                exercise.setAccuracy("0%");
            }
        }
        log.debug("习题列表");
        return pages;
    }

    /**
     * 判断前台传过来的系部ID和专业ID填充到最终要查询的系部ID和专业ID集合
     *
     * @param specIds 存储系部最终要查询的ID
     * @param specIds 存储专业最终要查询的ID
     * @param deptId  系部ID
     * @param specId  专业ID
     * @author lyj
     * @since 2015-2-26
     */
    public void checkParameter(List<Long> specIds, List<Long> deptIds, Long specId, Long deptId) {
        if (specId > 0) {
            log.debug("如果专业ID大于零:specId > 0");
            specIds.add(specId);
        }

        if (deptId > 0) {
            log.debug("如果部门ID大于零：deptId > 0");
            deptIds.add(deptId);
        }
    }

    /**
     * 判断系部ID是否是用户权限内的
     *
     * @param deptIds 存储系部最终要查询的ID
     * @param specIds 存储专业最终要查询的ID
     * @param deptId  系部ID
     * @param specId  专业ID
     * @author lyj
     * @since 2015-2-13
     */
    public void fillterParameter(List<Long> deptIds, List<Long> specIds, Long deptId, Long specId) {

        //根据用户ID，用户角色获取用户所管理的专业ID集合。
        List<Long> userSpecIds = roleDao.getSpecIdByRoleId(webContext.getUserId(), webContext.getRole());

        //查系部全部，专业全部
        if (deptId == 0 && specId == 0) {
            log.debug("查看全部专业、全部系部：deptId == 0 && specId == 0");
            specIds.addAll(userSpecIds);
        } else if (deptId > 0 && specId == 0) { //查系部某个，专业全部
            log.debug("查看某个系部的全部专业：deptId > 0 && specId == 0");
            deptIds.add(deptId);
            specIds.addAll(userSpecIds);
        } else if (specId > 0) { //查系部全部/某个，专业某个；系部全部，专业某个的情况不存在
            log.debug("查系部全部/某个，专业某个；系部全部，专业某个的情况不存在:specId > 0");
            if (userSpecIds.contains(specId)) {  //如果前台查看的专业是用户所管理的专业
                log.debug("如果用户管理的专业包含前台查看的专业：userSpecIds.contains(specId)");
                specIds.add(specId);
            } else {
                log.debug("如果用户管理的专业不包含前台查看的专业");
                specIds.add(Long.valueOf(0));
            }
        } else {
            log.debug("参数错误");
            log.warn("参数错误：deptId->{},specId->{}", deptId, specId);
            throw new ServiceException();
        }

    }

    /**
     * 获取习题列表明细
     *
     * @param exerciseId 习题ID
     * @return Exercise
     * @author lyj
     * @since 2015-2-9
     */
    public Exercise getExerDetail(long exerciseId) {
        if(webContext.getRole() != Roles.SUPER_ADMIN) {
            log.debug("如果登录用户不是超级管理员：webContext.getRole() != Roles.SUPER_ADMIN");
            //验证用户是否有权限查看该习题明细
            Exercise exerciseCheck = exerciseDao.checkDetail(exerciseId); //根据习题ID获取习题创建人信息和是否公开信息
            if (exerciseCheck.getParentId() == 0) { //根据父级ID判断是否为子习题
                if (exerciseCheck.getCreatorId().longValue() != webContext.getUserId().longValue()) { //如果该习题不是登录员工创建的习题
                    log.debug("如果该习题不是登录员工创建的习题:exerciseCheck.getCreatorId() != webContext.getUserId()");
                    if (exerciseCheck.getIsOpen() != 1) { //如果该习题不是公开的习题

                        log.debug("如果该习题不是公开的习题:exerciseCheck.getIsOpen() != 1");
                        log.warn("登录用户没有权限查看该习题明细exerciseId->{},userId->{},isOpen->{},creatorId->{}", exerciseId, webContext.getUserId(), exerciseCheck.getIsOpen(), exerciseCheck.getCreatorId());
                        throw new ServiceException("登录用户没有权限查看该习题明细");
                    }
                }
            }
        }

        //获取题干、题型、分数、专业ID、是否公开、难度、下载地址、预览地址
        Exercise exercise = exerciseDao.getExerDetail(exerciseId);

        if (exercise != null) {

            log.debug("如果exercise不为null：exercise != null");
            //如果父级ID等于0，证明是主习题。
            if (exercise.getParentId() == 0) {

                log.debug("如果父级ID等于0，证明是主习题:exercise.getParentId() == 0");
                //获取习题使用次数、回答次数、回答正确次数（注意参数顺序就是获取的数据顺序）
                List<Long> longList = exerciseCountDao.getMulti(exerciseId, ExerciseCountDao.USE_TOTAL, ExerciseCountDao.ANSWER_TOTAL, ExerciseCountDao.RIGHT_TOTAL);
                //获取习题热度
                exercise.setUseTotal(longList.get(0));

                //如果回答次数不等于零
                if (longList.get(1) != 0) {

                    log.debug("如果回答次数不等于零:longList.get(1) != 0");
                    double accuracy = (double)longList.get(2) / longList.get(1);//回答正确数 除以 回答总数

                    //四舍五入df.format(0.356) = 0.36
                    DecimalFormat df = new DecimalFormat("##%");
                    //获取习题正确率
                    exercise.setAccuracy(df.format(accuracy));

                } else { //如果回答次数为0，则不存在正确率
                    log.debug("如果回答次数为0，则不存在正确率");
                    exercise.setAccuracy("0%");
                }


                //获取知识点
                exercise.setKps(getKnowledge(exerciseId));

                //获取提示
                exercise.setTipList(exerciseTipDao.getExerciseTips(exerciseId));

                //获取常规解析
                exercise.setAnalysisList(getAnalysis(exerciseId));

                //获取引用资源解析数量
                exercise.setAnalysisResCount(Long.valueOf(exerciseResDao.getResIds(exerciseId).size()));

                //获取子级习题ID集合
                List<Long> sonIds = exerciseRelationDao.getSonIds(exerciseId);

                //集合是空不需要if判断
                if (sonIds.size() > 0) {
                    log.debug("集合是空不需要if判断:sonIds.size() > 0");
                    //声明存储子习题的容器
                    List<Exercise> sonExercises = new ArrayList<>();
                    log.debug("遍历子习题ID集合");
                    for (Long sonId : sonIds) {
                        //根据习题ID调用方法本身获取习题明细，然后放到sonExercise
                        sonExercises.add(getExerDetail(sonId));
                    }
                    exercise.setSubExercises(sonExercises);
                }
            }

            //如果习题类型是填空题，返回填空集合
            if (exercise.getType() == ExerciseType.COMPLETION) {
                log.debug("如果习题类型是填空题，返回填空集合:exercise.getType() == ExerciseType.COMPLETION");
                //获取习题填空
                exercise.setBlanks(exerciseBlankDao.getExerciseBlanks(exerciseId));
                //获取填空答案
                log.debug("获取所有填空答案");
                for (ExerciseBlank exerciseBlank : exercise.getBlanks()) {
                    exerciseBlank.setSolutions(blankSolutionDao.getBlankSolutions(exerciseBlank.getId()));
                }
            }

            //如果习题类型是选择题或是判断题
            if (exercise.getType() == ExerciseType.SINGLE_CHOICE || exercise.getType() == ExerciseType.MULTIPLE_CHOICE || exercise.getType() == ExerciseType.TRUE_OR_FALSE) {
                log.debug("如果习题类型是选择题或是判断题:exercise.getType() == ExerciseType.SINGLE_CHOICE || exercise.getType() == ExerciseType.MULTIPLE_CHOICE || exercise.getType() == ExerciseType.TRUE_OR_FALSE");
                //获取习题选项
                exercise.setOptions(exerciseOptionDao.getExerciseOptions(exerciseId));
            }

        }

        log.debug("习题明细");
        //返回填充后的实体
        return exercise;
    }

    /**
     * 根据习题ID获取附件预览信息
     * @param exerciseId  习题ID
     * @return
     * @author lyj
     * @since 2015-3-9
     */
    public Map<String, Object> getShowAnnex(long exerciseId){
        Map<String, Object> map = exerciseDao.getShowAnnex(exerciseId); //获取附件信息

        if (map.get("showUrl") == null){
            map.put("showUrl", "");
        }

        if (map.get("name") == null){
            map.put("name", "");
        }

        String downloadAddress = map.get("downloadUrl").toString();
        if (downloadAddress != null && !downloadAddress.equals("")){
            int point = downloadAddress.lastIndexOf("."); //获取点的下标
            //附件后缀名
            map.put("suffix", downloadAddress.substring(point + 1));

            return map;
        } else {
            log.debug("该习题没有附件:exerciseId->{}", exerciseId);
            throw new ServiceException("该习题没有附件");
        }
    }

    /**
     * 获取习题明细集合
     * @param exerciseIds 习题编号集合
     * @return Map
     *
     * @author fanruiping
     * @since 2015-03-26
     */
    public Map<Long,Exercise> getDetailByIds(List<Long> exerciseIds){
        List<Exercise> entities = exerciseDao.getByIds(exerciseIds);

        Map<Long,Exercise> result = new HashMap<>();

        log.debug("循环习题集合插入结果集合中:Exercise exercise : entities");
        //循环习题集合插入结果集合中
        for(Exercise exercise : entities){
            result.put(exercise.getId(),exercise);
        }

        return result;
    }

    /**
     * 获取习题的填空数（多个习题）
     * @param exerciseIds 习题编号集合
     * @return Map
     *
     * @author fanruiping
     * @since 2015-03-26
     */
    public Map<Long,Integer> getExerciseAnswerCount(final List<Long> exerciseIds){
        //习题编号集合是否为空
        if (exerciseIds.isEmpty()){
            log.debug("习题编号集合为空:exerciseIds.isEmpty()");

            return new HashMap<>();
        }

        //获取习题填空数
        List<ExerciseBlank> exerciseBlanks = exerciseBlankDao.getExerciseBlanksByIds(exerciseIds);

        Map<Long,Integer> result = new HashMap<>();

        log.debug("循环习题填空集合插入习题填空数:ExerciseBlank exerciseBlank : exerciseBlanks");
        for(ExerciseBlank exerciseBlank : exerciseBlanks){
            //判断是否已包含指定习题
            if(result.containsKey(exerciseBlank.getExerciseId())){
                log.debug("结果集合包含指定习题数据:result.containsKey(exerciseBlank.getExerciseId())");

                result.put(exerciseBlank.getExerciseId(),result.get(exerciseBlank.getExerciseId())+exerciseBlank.getAnswerCount());
            }else{
                log.debug("结果集合不包含指定习题数据");

                result.put(exerciseBlank.getExerciseId(),exerciseBlank.getAnswerCount());
            }
        }

        return result;
    }

    /**
     * 作业选习题列表
     * @param userId 当前用户编号
     * @param knowledges 知识点字符串
     * @param majorId 专业编号
     * @param source 来源（0：系统题库，1：我的题库）
     * @param type 题型（0：全部，1：单选，2：多选，3：判断，4：填空，5：简答，6：阅读，7：阅读多问）
     * @param pageNum 页码
     * @param pageSize 每页显示条数
     * @return Page
     *
     * @author fanruiping
     * @since 2015-03-27
     */
    public Page<Exercise> getListForTest(Long userId,String knowledges,Long majorId,int source,int type,int pageNum,int pageSize){
        Set<String> KnowledgeIds = StringUtils.commaDelimitedListToSet(knowledges);

        List<Long> exerciseIds = new ArrayList<>();

        log.debug("循环知识点编号集合获取习题编号集合:String id : KnowledgeIds");
        //循环获取包含知识点集合的习题编号集合
        for (String id : KnowledgeIds){
            Set<Long> idSet = kpExerciseDao.getExerciseIds(Long.valueOf(id));

            exerciseIds.addAll(idSet);
        }

        //判断习题编号集合是否为空
        if(exerciseIds.isEmpty()){
            log.debug("习题编号集合为空:exerciseIds.isEmpty()");

            return new Page<>();
        }

        //获取答案数
        Map<Long,Integer> answerCount = getExerciseAnswerCount(exerciseIds);

        //获取子题集合
        Map<Long,List<Exercise>> crossheads = this.getCrosshead(exerciseIds);

        Page<Exercise> page = exerciseDao.getListForTest(userId,exerciseIds,majorId,source,type,pageNum,pageSize);

        log.debug("循环习题列表插入答案数和子题集合");
        //循环为习题插入答案数和子题集合
        for(Exercise exercise : page.getItems()){
            //答案数
            if(answerCount != null && answerCount.containsKey(exercise.getId())){
                log.debug("答案数集合中包含习题编号");

                exercise.setBlankCnt(answerCount.get(exercise.getId()));
            }

            //插入子题
            if(crossheads != null && crossheads.containsKey(exercise.getId())){
                log.debug("子题集合中包含习题编号");

                exercise.setSubExercises(crossheads.get(exercise.getId()));
            }
        }

        return page;
    }

    /**
     * 获取习题明细(创建作业选题明细)
     *
     * @param id 习题编号
     * @return Exercise
     *
     * @author fanruiping
     * @since 2015-03-31
     */
    @Cacheable(value = "exercise", key = "'exercise:detail'.concat(#id)")
    public Exercise getDetail(long id) {

        //获取题干、题型、分数、专业ID、是否公开、难度、下载地址、预览地址
        Exercise exercise = exerciseDao.getExerDetail(id);

        //判断指定习题是否存在
        if (exercise != null) {
            log.debug("指定习题的详细信息存在:exercise != null");

            //如果习题类型是阅读理解(一题多问)
            if (exercise.getType() == ExerciseType.READING_COMPREHENSION_MULTI) {
                log.debug("习题类型为阅读理解多题:exercise.getType() == ExerciseType.READING_COMPREHENSION_MULTI");

                //获取知识点
                exercise.setKps(getKnowledge(id));

                //获取子级习题ID集合
                List<Long> sonIds = exerciseRelationDao.getSonIds(id);

                //集合是空不需要if判断
                if (sonIds.size() > 0) {
                    log.debug("子习题编号集合不为空:sonIds.size() > 0");

                    //获取子习题详细信息集合
                    List<Exercise> sonExerciseDetail = exerciseDao.getByIds(sonIds);

                    //插入子习题明细集合
                    exercise.setSubExercises(sonExerciseDetail);
                }
                //如果习题类型是填空题，返回填空集合
            } else if (exercise.getType() == ExerciseType.COMPLETION) {
                log.debug("习题类型为填空题:exercise.getType() == ExerciseType.COMPLETION");

                //获取习题填空
                exercise.setBlanks(exerciseBlankDao.getExerciseBlanks(id));

                log.debug("循环习题填空集合插入填空答案");
                //获取填空答案
                for (ExerciseBlank exerciseBlank : exercise.getBlanks()) {
                    exerciseBlank.setSolutions(blankSolutionDao.getBlankSolutions(exerciseBlank.getId()));
                }
                //如果习题类型是选择题或是判断题
            } else if (exercise.getType() == ExerciseType.SINGLE_CHOICE || exercise.getType() == ExerciseType.MULTIPLE_CHOICE
                    || exercise.getType() == ExerciseType.TRUE_OR_FALSE) {
                log.debug("习题类型为单选、多选或判断");

                //获取习题选项
                exercise.setOptions(exerciseOptionDao.getExerciseOptions(id));
            }
        }

        return exercise;
    }

    /**
     * 获取指定习题集合的子题集合
     * @param parentIds 习题编号集合
     * @return Map
     *
     * @author fanruiping
     * @since 2015-04-06
     */
    public Map<Long,List<Exercise>> getCrosshead(final List<Long> parentIds){
        List<Exercise> exerciseList = exerciseDao.getCrosshead(parentIds);

        Map<Long,List<Exercise>> result = new HashMap<>();

        //判断习题集合是否为空
        if(exerciseList.isEmpty()){
            log.debug("习题集合为空:exerciseList.isEmpty()");

            return result;
        }

        List<Long> exerciseIds = new ArrayList<>();

        log.debug("循环习题集合获取习题编号集合:Exercise exercise : exerciseList");
        for (Exercise exercise : exerciseList){
            exerciseIds.add(exercise.getId());
        }

        //获取答案数
        Map<Long,Integer> answerCount = getExerciseAnswerCount(exerciseIds);

        log.debug("循环习题集合插入答案数和子题集合:Exercise exercise : exerciseList");
        for(Exercise exercise : exerciseList){
            //插入答案数
            if(answerCount != null && answerCount.containsKey(exercise.getId())){
                log.debug("习题答案数集合包含指定习题编号:answerCount != null && answerCount.containsKey(exercise.getId())");

                exercise.setBlankCnt(answerCount.get(exercise.getId()));
            }

            //插入子题集合
            if(result.containsKey(exercise.getParentId())){
                log.debug("结果集合中包含指定习题的父习题:result.containsKey(exercise.getParentId())");

                result.get(exercise.getParentId()).add(exercise);
            }else{
                log.debug("结果集合中不包含指定习题的父习题");

                List<Exercise> list = new ArrayList<>();
                list.add(exercise);

                result.put(exercise.getParentId(),list);
            }
        }

        return result;
	}

    /**
     * 绑定习题及学生答题明细
     * 
     * @param exercise 习题
     * @param studentAnswer 学生答题
     * @param showAnswer 控制显示标准答案
     *                 
     * @author cx
     * @since 2015/4/3
     */
    public void loadExerciseWithAnswer(Exercise exercise, StudentAnswer studentAnswer, boolean showAnswer) {
        int type = exercise.getType();

        //解析答题内容重组为前端期望的格式
        if (type == ExerciseType.SINGLE_CHOICE || type == ExerciseType.MULTIPLE_CHOICE) {
            log.debug("选择题答题明细:type == ExerciseType.SINGLE_CHOICE || type == ExerciseType.MULTIPLE_CHOICE");

            studentAnswerService.loadChoice(exercise, studentAnswer, showAnswer); //选择题
            //隐藏答题内容，避免暴露答题内容存储结构
            studentAnswer.setAnswerContent(null);
        }
        else if (type == ExerciseType.TRUE_OR_FALSE) { //判断题
            log.debug("判断题答题明细:type == ExerciseType.TRUE_OR_FALSE");

            studentAnswerService.loadTrueOrFalse(exercise, studentAnswer, showAnswer);
            //隐藏答题内容，避免暴露答题内容存储结构
            studentAnswer.setAnswerContent(null);
        }
        else if (type == ExerciseType.COMPLETION) { //填空题
            log.debug("填空题答题明细:type == ExerciseType.COMPLETION");

            studentAnswerService.loadCompletion(exercise, studentAnswer, showAnswer);
            //隐藏答题内容，避免暴露答题内容存储结构
            studentAnswer.setAnswerContent(null);
        }

        //简答题和阅读理解题的学生答案及教师回复在StudentAnswer中
        exercise.setStudentAnswer(studentAnswer);
    }

    /**
     * 获取作业习题明细
     * 
     * @param exerciseId 习题ID
     * @param showTips 显示提示控制
     * @param showTestRefInfo 显示知识点解析控制
     * @return Exercise
     * 
     * @author cx
     * @since 2015/4/16
     */
    public Exercise getTEDetail(final long exerciseId, final boolean showTips, final boolean showTestRefInfo) {
        //获取习题的基本信息
        Exercise exercise = exerciseDao.getExerDetail(exerciseId);

        if (exercise == null) {
            log.debug("习题为null:exercise == null");

            log.warn("不存在的习题记录:exerciseId->{}", exerciseId);
            throw new ServiceException("访问了不存在的习题");
        }

        if (exercise.getParentId() == 0) { //主习题
            log.debug("主习题判断:exercise.getParentId() == 0");

            //热度
            exercise.setUseTotal(exerciseCountDao.getTotal(exerciseId, ExerciseCountDao.USE_TOTAL));

            if (showTips) {
                log.debug("获取提示:showTips || overtime");
                //提示
                exercise.setTipList(exerciseTipDao.getExerciseTips(exerciseId));
            }

            if (showTestRefInfo) { //答题敏感信息
                log.debug("作业已过时:overtime");

                //获取知识点
                exercise.setKps(getKnowledge(exerciseId));

                //获取常规解析
                exercise.setAnalysisList(getAnalysis(exerciseId));

                //获取资源解析的总数
                exercise.setAnalysisResCount(exerciseResDao.getTotal(exerciseId));
            }
        }

        log.debug("获取作业习题明细");
        return exercise;
    }

    /**
     * 获取作业习题及学生答题记录
     * 
     * @param studentId 学生ID
     * @param testId 作业ID
     * @param exerciseId 习题ID
     * @param showTips 显示提示控制
     * @param showTestRefInfo 作业到期控制
     * @param showAnswer 显示答案控制
     * @return Exercise
     * 
     * @author cx
     * @since 2015/4/16
     */
    public Exercise getTEWithAnswer(final long studentId, final long testId, final long exerciseId,
                                    final boolean showTips, final boolean showTestRefInfo, final boolean showAnswer) {
        //获取作业习题明细
        Exercise exercise = getTEDetail(exerciseId, showTips, showTestRefInfo);
        //获取学生的答题记录
        StudentAnswer studentAnswer = studentAnswerRedisDao.getStudentAnswer(studentId, testId, exerciseId);
        //加载学生作业习题答题
        loadExerciseWithAnswer(exercise, studentAnswer, showAnswer);

        //获取子习题ID集合
        List<Long> sonIds = exerciseRelationDao.getSonIds(exerciseId);
        if (sonIds != null && !sonIds.isEmpty()) {
            log.debug("子习题集合非空:sonIds != null && !sonIds.isEmpty()");

            List<Exercise> sonExercises = new ArrayList<>(sonIds.size());

            log.debug("循环子习题集合");
            for (Long sonId : sonIds) {
                //递归获取子习题信息
                sonExercises.add(getTEWithAnswer(studentId, testId, sonId, showTips, showTestRefInfo, showAnswer));
            }
            exercise.setSubExercises(sonExercises);
        }

        //隐蔽答题后的得分、对错结果、教师评语
        if (studentAnswer != null && !showTestRefInfo) {
            log.debug("隐蔽敏感的答题信息:studentAnswer != null && !showTestRefInfo");

            studentAnswer.setScore(null);
            studentAnswer.setStatus(null);
            studentAnswer.setTeacherComment(null);
        }

        //获取习题在作业中的分值,覆盖习题的默认分值
        TestExercise testExercise = exerciseDao.getTestExercise(testId, exerciseId);
        exercise.setScore((long) testExercise.getScore());

        log.debug("获取作业习题及学生答题记录");
        return exercise;
    }

    /**
     * 转换html到纯文本
     * @param html HTML内容
     * @return String
     *
     * @author wyh
     * @since 2015-04-02
     */
    private String htmlToText(String html){
        return StringEscapeUtils.unescapeHtml4(html.replaceAll("</?[^>]+>", ""));
    }

    /**
     * 统计不同类型习题的总数
     *
     * @author fanruiping
     * @since 2015-05-06
     */
    public Map<String, Object> getStatisticsByCreator() {
        //判断当前用户角色
        if(webContext.getRole() == Roles.COLLEGE_USER || webContext.getRole() == Roles.STUDENT){
            log.debug("当前用户是学生或院级用户，无权查看该数据");

            throw new ServiceException("学生和院级用户不允许看该数据");
        }

        //获取统计结果
        List<Map<String, Object>> statisticsResult = exerciseDao.findTotalByTypeAndUser(webContext.getUserId());

        //转换统计结果格式，便于重组
        Map<Integer, Long> typeTotal = Maps.newHashMap();

        log.debug("循环获取各题型的习题数量");
        //循环获取各题型的习题数量
        for (Map<String, Object> item : statisticsResult) {
            typeTotal.put((int) item.get("type"), (long) item.get("total"));
        }

        //题型习题统计结果
        Map<String, Object> result = Maps.newHashMap();

        //定义习题总数量
        long total = 0;

        //获取各种习题类型总数
        List<Map<String, Object>> info = Lists.newArrayList();

        log.debug("循环获取题型及习题数量");
        //循环获取类型题型及习题数量
        for (int i = ExerciseType.SINGLE_CHOICE; i <= ExerciseType.READING_COMPREHENSION_MULTI; i++) {
            Map<String, Object> infoItem = Maps.newHashMap();

            infoItem.put("type", i);
            Long count = typeTotal.get(i);
            count = count == null ? 0 : count;
            infoItem.put("count", count);

            info.add(infoItem);

            total += count;
        }

        result.put("total", total);
        result.put("info", info);

        return result;
    }

    /**
     * 各系部下习题资源数量
     * @param keyword 搜索关键字
     * @return List
     *
     * @author fanruiping
     * @since 2015-05-06
     */
    public List<Map<String,Object>> getTotalForDepartment(String keyword){
        //判断当前用户角色为学生或院级用户
        if(webContext.getRole() == Roles.STUDENT || webContext.getRole() == Roles.COLLEGE_USER){
            log.debug("当前用户是学生或院级用户，无权查看该数据");

            throw new ServiceException("当前用户无权查看该数据");
        }

        Long userId = webContext.getUserId();//当前用户编号

        Map<Long,Long> totalExercise = exerciseDao.getTotalForDepartment(userId, keyword);

        //系部资源数据统计为空
        if(totalExercise.isEmpty()){
            log.debug("系部资源数据统计为空:totalExercise.isEmpty()");

            return new ArrayList<>();
        }

        List<Department> names = departmentDao.getByIds(totalExercise.keySet());

        List<Map<String,Object>> result = new ArrayList<>();

        log.debug("循环获取系部编号、代码、名称和资源数量:Department department : names");
        //循环获取系部编号、代码、名称和资源数量
        for (Department department : names){
            Map<String,Object> data = new HashMap<>();
            data.put("id",department.getId());
            data.put("code",department.getNumber());
            data.put("name",department.getName());
            data.put("count",totalExercise.get(department.getId()));

            result.add(data);
        }

        return result;
    }

    /**
     * 各专业下习题资源数量
     * @param keyword 搜索关键字
     * @param departNum 系部编码
     * @return List
     *
     * @author fanruiping
     * @since 2015-05-06
     */
    public List<Map<String,Object>> getTotalForSpecialty(String keyword,String departNum){
        //判断当前用户角色为学生或院级用户
        if(webContext.getRole() == Roles.STUDENT || webContext.getRole() == Roles.COLLEGE_USER){
            log.debug("当前用户是学生或院级用户，无权查看该数据");

            throw new ServiceException("当前用户无权查看该数据");
        }

        Long userId = webContext.getUserId();//当前用户编号
        List<Long> specialtyIds = new ArrayList<>();//专业编号集合
        List<Specialty> specialties = new ArrayList<>();

        //限制指定系部下专业
        if(!"0".equals(departNum)){
            log.debug("指定系部编码:!0.equals(departNum)");

            specialties = specialtyDao.getByDeptNum(departNum);

            log.debug("循环获取专业编号:Specialty specialty : specialties");
            //循环获取专业编号
            for (Specialty specialty : specialties){
                specialtyIds.add(specialty.getId());
            }
        }

        //获取各专业下习题资源数量
        Map<Long,Long> totalExercise = exerciseDao.getTotalForSpecialty(userId, keyword, specialtyIds);

        List<Map<String,Object>> result = new ArrayList<>();

        //专业集合为空
        if(specialties.isEmpty()){
            log.debug("专业数据集合为空:specialties.isEmpty()");

            Map<Long,String> names = specialtyDao.getNamesByIds(totalExercise.keySet());

            log.debug("循环获取专业统计数据:Long specialtyId : totalExercise.keySet()");
            //循环获取专业统计数据
            for (Long specialtyId : totalExercise.keySet()){
                Map<String,Object> data = new HashMap<>();
                data.put("id",specialtyId);
                data.put("name",names.get(specialtyId));
                data.put("count", totalExercise.get(specialtyId));

                result.add(data);
            }
        }else {
            log.debug("专业数据集合不为空:!specialties.isEmpty()");

            log.debug("循环获取专业编号、名称和习题数量:Specialty specialty : specialties");
            //循环获取专业编号、名称和习题数量
            for (Specialty specialty : specialties){
                Map<String,Object> data = new HashMap<>();
                data.put("id",specialty.getId());
                data.put("name",specialty.getName());
                data.put("count",totalExercise.get(specialty.getId()) == null ? 0 : totalExercise.get(specialty.getId()));

                result.add(data);
            }
        }

        return result;
    }

    /**
     * 我的习题资源列表
     * @param departId 系部编号
     * @param specId 专业编号
     * @param types 题型集合
     * @param keyword 搜索关键字
     * @param page 页码
     * @param pageSize 分页
     * @return Page
     *
     * @author fanruiping
     * @since 2015-05-06
     */
    public Page<Exercise> getListForMine(Long departId,Long specId,List<Integer> types,String keyword,int page,int pageSize){
        Page<Exercise> exercisePage = exerciseDao.getListForMine(webContext.getUserId(),departId,specId,keyword,types,page,pageSize);

        //判断是否获取到习题列表
        if(exercisePage.getItems().isEmpty()){
            log.debug("习题列表为空:exercisePage.getItems().isEmpty()");

            return exercisePage;
        }

        //习题编号集合
        List<Long> exerciseIds = new ArrayList<>();

        log.debug("循环习题列表获取习题编号");
        //循环习题列表获取习题编号
        for(Exercise exercise : exercisePage.getItems()){
            exerciseIds.add(exercise.getId());
        }

        //获取习题资源集合包含的知识点集合
        Map<Long,List<KnowledgePoint>> exerciseKps = this.getKpsByExercsieIds(exerciseIds);

        //四舍五入df.format(0.356) = 36%
        DecimalFormat df = new DecimalFormat("##");

        log.debug("循环习题列表，插入知识点、正确率、热度等数据");
        for(Exercise exercise : exercisePage.getItems()){
            //插入习题知识点集合
            exercise.setKps(exerciseKps.get(exercise.getId()));

            //获取习题使用次数、回答次数、回答正确次数（注意参数顺序就是获取的数据顺序）
            List<Long> longList = exerciseCountDao.getMulti(exercise.getId(), ExerciseCountDao.USE_TOTAL, ExerciseCountDao.ANSWER_TOTAL, ExerciseCountDao.RIGHT_TOTAL);
            //习题热度
            exercise.setUseTotal(longList.get(0));

            //习题正确率 = 回答正确数 除以 回答总数
            if (longList.get(1) != 0) {
                log.debug("回答总数不为0:longList.get(1) != 0");

                //回答正确数 除以 回答总数
                exercise.setAccuracy(df.format((double)longList.get(2) / longList.get(1)));
            } else {
                log.debug("回答总数为0:longList.get(1) == 0");

                exercise.setAccuracy("0");
            }
        }

        return exercisePage;
    }

    /**
     * 获取多个习题资源的知识点集合
     * @param exerciseIds 习题资源编号集合
     * @return Map
     *
     * @author fanruiping
     * @since 2015-05-06
     */
    public Map<Long,List<KnowledgePoint>> getKpsByExercsieIds(List<Long> exerciseIds){
        Set<Long> kpIds = exerciseKpDao.getKpIdsByExerciseIds(exerciseIds);

        List<KnowledgePoint> kps = kpService.getAll(new ArrayList<Long>(kpIds));

        Map<Long,KnowledgePoint> knowledgePointMap = new HashMap<>();

        log.debug("循环知识点集合获取知识点实体");
        //循环获取知识点实体
        for(KnowledgePoint knowledgePoint : kps){
            knowledgePointMap.put(knowledgePoint.getId(),knowledgePoint);
        }

        Map<Long,List<KnowledgePoint>> exerciseKps = new HashMap<>();

        log.debug("循环获取习题编号");
        //循环获取习题编号
        for(Long exerciseId : exerciseIds){
            List<Long> exerciseKpIds = exerciseKpDao.getKpIds(exerciseId);

            log.debug("循环获取习题包含的知识点编号");
            for(Long id : exerciseKpIds){
                //判断习题知识点集合中，是否包含指定习题
                if(!exerciseKps.containsKey(exerciseId)){
                    log.debug("习题知识点集合中不包含指定习题:!exerciseKps.containsKey(exerciseKpIds)");

                    List<KnowledgePoint> knowledgePoints = new ArrayList<>();

                    knowledgePoints.add(knowledgePointMap.get(id));

                    exerciseKps.put(exerciseId,knowledgePoints);
                }else{
                    log.debug("习题知识点集合中包含指定习题:exerciseKps.containsKey(exerciseKpIds)");

                    exerciseKps.get(exerciseId).add(knowledgePointMap.get(id));
                }
            }
        }

        return exerciseKps;
    }
}
